#ifndef NIGHT_LAMBDA_CORE
#define NIGHT_LAMBDA_CORE

#include "detail.hpp"

namespace night { namespace lambda { namespace detail { 

template< typename T1, typename T2 >
inline typename detail::join<T1,T2>::result_type operator , ( T1 & x, T2 & y ) { 
	return detail::join<T1,T2>::apply (x,y); 
}
template< typename T1, typename T2 >
inline typename detail::join<T1 const,T2>::result_type operator , ( T1 const & x, T2 & y ) { 
	return detail::join<T1 const,T2>::apply (x,y); 
}
template< typename T1, typename T2 >
inline typename detail::join<T1,T2 const>::result_type operator , ( T1 & x, T2 const & y ) { 
	return detail::join<T1,T2 const>::apply (x,y); 
}
template< typename T1, typename T2 >
inline typename detail::join<T1 const,T2 const>::result_type operator , ( T1 const & x, T2 const & y ) { 
	return detail::join<T1 const,T2 const>::apply (x,y); 
}

template< int i > struct arg;
template< typename T > struct var;
template< typename Var, class Args > struct select;
template< class Expr, class Vars, class Args > struct exec;
template< typename Expr, class Vars > struct proxy;

} } } // end namespace night

namespace night { namespace lambda { 

struct Assign;
struct Deref;

// not in detail namespace to avoid ADL when deriving from Lambda

template< class Expr > struct Lambda {

//	friend Expr; // doesn't work on gcc 3.4
//private:

#ifdef ENABLE_DEBUG
protected:
	~Lambda () {}
#endif

public:

	Expr & deref () { return static_cast<Expr &> (*this); }
	Expr const & deref () const { return static_cast<Expr const &> (*this); }

	operator Expr & () { return this->deref (); }
	operator Expr const & () const { return this->deref (); }

	template< typename T1, typename T2, typename T3 >
	detail::proxy< Expr, detail::tuple<T1,T2,T3> > operator [] ( detail::tuple<T1,T2,T3> const & vars ) const { 
		return detail::proxy< Expr, detail::tuple<T1,T2,T3> > ( this->deref (), vars ); 
	}

	template< typename Var >
	detail::proxy< Expr, detail::tuple<Var const> > operator [] ( Lambda<Var> const & var ) const {
		return detail::proxy< Expr, detail::tuple<Var const> > ( this->deref (), detail::make_tuple ( var.deref () ) );
	}

	template< typename T1 >
	detail::proxy< Expr, detail::tuple<T1 const> > args ( T1 const & x ) const {
		return detail::proxy< Expr, detail::tuple<T1 const> > ( this->deref (), detail::make_tuple (x) );
	}

	template< typename T1, typename T2 >
	detail::proxy< Expr, detail::tuple<T1 const,T2 const> > args ( T1 const & x, T2 const & y ) const {
		return detail::proxy< Expr, detail::tuple<T1 const,T2 const> > ( this->deref (), detail::make_tuple (x,y) );
	}

	template< typename T1, typename T2, typename T3 >
	detail::proxy< Expr, detail::tuple<T1 const,T2 const,T3 const> > args ( T1 const & x, T2 const & y, T3 const & z ) const {
		return detail::proxy< Expr, detail::tuple<T1 const,T2 const,T3 const> > ( this->deref (), detail::make_tuple (x,y,z) );
	}

	template<typename T>
	detail::proxy< lambda::Assign, detail::tuple<Expr,T const> > operator = ( T const & val );
	template<typename T>
	detail::proxy< lambda::Assign, detail::tuple<Expr const,T const> > operator = ( T const & val ) const;

};

} } // end namespace night

namespace night { namespace lambda { namespace detail { 

template< int i > struct arg : lambda::Lambda< arg<i> > 
{	
	template< typename T1 = void, typename T2 = void, typename T3 = void >
	struct sig { 
		typedef typename detail::element<i-1,detail::tuple<T1,T2,T3> >::type result_type; 
	};

	using Lambda< arg >::operator=;

	detail::proxy< lambda::Deref, detail::tuple<arg<i> const> > operator * () const;
};

template< typename T > struct var : lambda::Lambda< var<T> > 
{
	T value;

	var ( T const & src ) : value (src) {}
	var & operator = ( T const & src ) { this->value = src; return (*this); }

	using Lambda< var >::operator=;

	T & operator * () { return this->value; }
	const T & operator * () const { return this->value; }

};

template< typename T, class Tuple >
struct select 
{
	typedef T result_type;

	static result_type & apply ( T & x, Tuple const & ) { return x; }
	static result_type const & apply ( T const & x, Tuple const & ) { return x; }
};

template< typename T, class Tuple >
struct select< T const, Tuple > : select<T,Tuple> {};

template< int i, class Tuple >
struct select< detail::arg<i>, Tuple > 
{
	typedef typename detail::element <i-1,Tuple>::type & result_type;

	static result_type apply ( detail::arg<i> const &, Tuple const & args ) { return detail::get<i-1> (args); }
};

template< typename T, class Tuple >
struct select< detail::var<T>, Tuple > 
{
	typedef T & result_type;

	static result_type apply ( detail::var<T> & x, Tuple const & ) { return x.value; }
};

template< typename T, class Tuple >
struct select< detail::var<T> const, Tuple > 
{
	typedef T const & result_type;

	static result_type apply ( detail::var<T> const & x, Tuple const & ) { return x.value; }
};

template< class Expr, class Vars, class Tuple >
struct select< detail::proxy<Expr,Vars>, Tuple >
{
	typedef typename detail::exec<Expr,Vars,Tuple>::result_type result_type;

	static result_type apply ( detail::proxy<Expr,Vars> const & p, Tuple const & args ) {
		return detail::exec<Expr,Vars,Tuple>::apply (p.expr,p.vars,args);
	}
};

template < int i, class Vars, class Tuple >
struct select < detail::proxy<detail::arg<i>,Vars>, Tuple >
{
	typedef typename detail::exec<typename detail::element<i-1,Tuple>::type,Vars,Tuple>::result_type result_type;

	static result_type apply ( detail::proxy<detail::arg<i>,Vars> const & p, Tuple const & args ) {
		return detail::exec<typename detail::element<i-1,Tuple>::type,Vars,Tuple>::apply (detail::get<i-1>(args),p.vars,args);
	}
};

template< class Expr, class T1, class Tuple >
struct exec<Expr, detail::tuple<T1>, Tuple>
{
	typedef detail::call<typename detail::select<Expr,Tuple>::result_type> expr_type;

	typedef typename expr_type::template sig< 
		typename detail::param<typename detail::select<T1,Tuple>::result_type>::type
	>::result_type result_type;

	static result_type apply ( Expr const & expr, detail::tuple<T1> const & vars, Tuple const & args ) 
	{ 
		return expr_type::apply ( detail::select<Expr,Tuple>::apply (expr,args), 
			detail::ref (detail::select<T1,Tuple>::apply (detail::get<0> (vars),args))
		); 
	}
};

template< class Expr, class T1, class T2, class Tuple >
struct exec<Expr, detail::tuple<T1,T2>, Tuple>
{
	typedef detail::call<typename detail::select<Expr,Tuple>::result_type> expr_type;

	typedef typename expr_type::template sig< 
		typename detail::param<typename detail::select<T1,Tuple>::result_type>::type,
		typename detail::param<typename detail::select<T2,Tuple>::result_type>::type
	>::result_type result_type;

	static result_type apply ( Expr const & expr, detail::tuple<T1,T2> const & vars, Tuple const & args ) 
	{ 
		return expr_type::apply ( detail::select<Expr,Tuple>::apply (expr,args),
			detail::ref (detail::select<T1,Tuple>::apply (detail::get<0> (vars),args)),
			detail::ref (detail::select<T2,Tuple>::apply (detail::get<1> (vars),args))
		); 
	}
};

template< class Expr, class T1, class T2, class T3, class Tuple >
struct exec<Expr, detail::tuple<T1,T2,T3>, Tuple>
{
	typedef detail::call<typename detail::select<Expr,Tuple>::result_type> expr_type;

	typedef typename expr_type::template sig< 
		typename detail::param<typename detail::select<T1,Tuple>::result_type>::type,
		typename detail::param<typename detail::select<T2,Tuple>::result_type>::type,
		typename detail::param<typename detail::select<T3,Tuple>::result_type>::type
	>::result_type result_type;

	static result_type apply ( Expr const & expr, detail::tuple<T1,T2,T3> const & vars, Tuple const & args ) 
	{ 
		return expr_type::apply ( detail::select<Expr,Tuple>::apply (expr,args),
			detail::ref (detail::select<T1,Tuple>::apply (detail::get<0> (vars),args)),
			detail::ref (detail::select<T2,Tuple>::apply (detail::get<1> (vars),args)),
			detail::ref (detail::select<T3,Tuple>::apply (detail::get<2> (vars),args))
		); 
	}
};

template< typename Expr >
struct expr_traits {
	typedef Expr type;
};

template< typename Expr >
struct expr_traits< lambda::Lambda<Expr> > {
	typedef Expr const & type;
};

template< typename Expr, class Tuple >
struct expr_traits< detail::proxy<Expr,Tuple> > {
	typedef detail::proxy<Expr,Tuple> const & type;
};

template< typename Expr, class Tuple >
struct proxy : lambda::Lambda< proxy<Expr,Tuple> > 
{
	typename detail::expr_traits<Expr>::type expr;
	Tuple vars;

	template< typename T1 = void, typename T2 = void, typename T3 = void >
	struct sig {
		typedef detail::exec< Expr, Tuple, detail::tuple<T1,T2,T3> > type;
		typedef typename type::result_type result_type;
	};

	proxy ( Expr const & f, Tuple const & t ) : expr (f), vars (t) {}

	using Lambda< proxy >::operator=;

	template< typename T1 >
	typename sig<T1>::result_type operator () ( T1 & x ) const { 
		return sig<T1>::type::apply ( this->expr, this->vars, detail::make_tuple (x) );
	}

	template< typename T1, typename T2 >
	typename sig<T1,T2>::result_type operator () ( T1 & x, T2 & y ) const { 
		return sig<T1,T2>::type::apply ( this->expr, this->vars, detail::make_tuple (x,y) );
	}

	template< typename T1, typename T2, typename T3 >
	typename sig<T1,T2,T3>::result_type operator () ( T1 & x, T2 & y, T3 & z ) const { 
		return sig<T1,T2,T3>::type::apply ( this->expr, this->vars, detail::make_tuple (x,y,z) );
	}
};

template< class Expr >
struct proxy0 : lambda::Lambda< proxy0<Expr> > {
	Expr const & expr;
	proxy0 ( Expr const & f ) : expr (f) {}

	typedef typename Expr::result_type result_type;
	
	result_type operator () () const { return this->expr (); }
};

template< class Expr >
struct proxy1 : lambda::Lambda< proxy1<Expr> > {
	Expr const & expr;
	proxy1 ( Expr const & f ) : expr (f) {}

	template< typename T1 > struct sig {
		typedef typename detail::call<Expr const>::template sig<T1>::result_type result_type;
	};
	
	template< typename T1 >
	typename sig<T1>::result_type operator () ( T1 & x ) const { 
		return detail::call<Expr const>::apply (this->expr,x); 
	}
};

template< class Expr >
struct proxy2 : lambda::Lambda< proxy2<Expr> > {
	Expr const & expr;
	proxy2 ( Expr const & f ) : expr (f) {}

	template< typename T1, typename T2 > struct sig {
		typedef typename detail::call<Expr const>::template sig<T1,T2>::result_type result_type;
	};
	
	template< typename T1, typename T2 >
	typename sig<T1,T2>::result_type operator () ( T1 & x, T2 & y ) const { 
		return detail::call<Expr const>::apply (this->expr,x,y); 
	}
};

template< class Expr >
struct proxy3 : lambda::Lambda< proxy3<Expr> > {
	Expr const & expr;
	proxy3 ( Expr const & f ) : expr (f) {}

	template< typename T1, typename T2, typename T3 > struct sig {
		typedef typename detail::call<Expr const>::template sig<T1,T2,T3>::result_type result_type;
	};
	
	template< typename T1, typename T2, typename T3 >
	typename sig<T1,T2,T3>::result_type operator () ( T1 & x, T2 & y, T3 & z ) const { 
		return detail::call<Expr const>::apply (this->expr,x,y,z); 
	}
};

template<typename Expr> struct Bind;

template< template<class> class F >
struct Fun
{
	template<typename Expr>
	F<Expr> const operator [] ( Expr const & expr ) { return F<Expr> (expr); }

	template<typename R>
	Bind< R(*)() > operator [] ( R (*fn) () ) { return Bind < R(*)() > (fn); }

	template<typename R, typename T1>
	Bind< R(*)(T1) > operator [] ( R (*fn) (T1) ) { return Bind < R(*)(T1) > (fn); }

	template<typename R, typename T1, typename T2>
	Bind< R(*)(T1,T2) > operator [] ( R (*fn) (T1,T2) ) { return Bind < R(*)(T1,T2) > (fn); }

	template<typename R, typename T1, typename T2, typename T3>
	Bind< R(*)(T1,T2,T3) > operator [] ( R (*fn) (T1,T2,T3) ) { return Bind < R(*)(T1,T2,T3) > (fn); }
};

inline Fun<proxy0> fun ( ) { return Fun<proxy0> (); }
inline Fun<proxy1> fun ( detail::arg<1> ) { return Fun<proxy1> (); }
inline Fun<proxy2> fun ( detail::arg<1>, detail::arg<2> ) { return Fun<proxy2> (); }
inline Fun<proxy3> fun ( detail::arg<1>, detail::arg<2>, detail::arg<3> ) { return Fun<proxy3> (); }

} } } // end namespace night

#endif // NIGHT_LAMBDA_CORE
